	var MC,Toolbar,Selection,Clipboard
	
/* -------------------------------------------------------------------------------------------
CHROME

------------------------------------------------------------------------------------------- */
	
	Object.extend(Chrome, {
		// Particular to our CSS layout
		wOffset:{x:150, y:86},
		// the mask element
		maskdiv:false,
		// The maincell element(s)
		mcs:['maincell'],
		// load json data, if any
		load:function(o){
			if(o) for(var k in o){
				if(['Toolbar', 'Selection', 'Clipboard'].include(k)) window[k] = new window[k+'Container'](o[k]);
				else if(k == 'maincell'){
					this.mcs.push(o[k]);
					this.resize();
				}
			};
			if(this.loaded) return;
			this.loaded = true;
			if(this.tlb && !Toolbar) this.load({Toolbar:this.tlb});
			this.resize();
			Event.observe(window, 'resize', this.resize);
			Smernel.fire('chrome:loaded');
		},
		resize:function(){
			var d = Chrome.innerDimensions()
			Chrome.rMainCells({width:d.width+'px', height:d.height +'px'});
		},
		unfillscreen:function(){
			Event.stopObserving(window, 'resize', this.resize);
			this.rMainCells({width:'100%', height:'100%'});
		},
		rMainCells:function(d){
			this.mcs.each(function(eid){
				$(eid).style.width = d.width;
				$(eid).style.height = d.height;
			});
		},
		innerDimensions:function(){
			return {width: window.getWidth()-Chrome.wOffset.x, height:window.getHeight()-Chrome.wOffset.y}
		},
		mkMCFrame:function(){
			window.MC = new ItemWindow($('maincell'), {
				Apps:Apps,
				Selection:Selection,
				offset:{x:this.wOffset.x, y:this.wOffset.y-26},
				view:($C('dirviewTable').value ? 1 : 0),
				resize:function(){
					if(this.resizing) return;
					this.resizing = true;
					var dims = this.getDimensions();
					// We only care if alteration is 20px or more
					var cx = dims.width - this.cur.width;
					var cy = dims.height - this.cur.height;
					if(((cx < 0 ? cx*-1 : cx) < 20)&&((cy < 0 ? cy*-1 : cy) < 20)) return this.resizing = false;
					setTimeout(function(){
						this.setFrameDims();
						Smernel.observe(this.id+':drawn', function(){ 
							this.resizing = false 
						}.bind(this));
						this.redraw();	
					}.bind(this), 150);
				},
				getDimensions:function(){
					return {width:window.getWidth()-Chrome.wOffset.x-26, height:window.getHeight()-Chrome.wOffset.y-30}
				},
				setFrameDims:function(){
					Object.extend(this.cur, this.getDimensions());
					this.body.style.height = this.cur.height + 'px';
					this.body.style.width = this.cur.width + 'px';
				},
				loaded:function(){
					Chrome.unfillscreen();
					Event.observe(window, 'resize', this.resize.bind(this));
					this.setFrameDims();
				},
				switchView:function(v){
					$C('dirviewTable').value = v ? 1 : '';
					$C('dirviewTable').write();
					this.switchTo(v);
				}
			});
			return window.MC;
		},
		maskcallers:{},
		maskcalls:0,
		mask:function(caller){
			if(this.maskcallers[caller]) return;
			this.maskcallers[caller] = true;
			this.maskcalls++;
			if(this.maskcalls > 1) return;
			$('msk').show();
		},
		unmask:function(caller){
			if(!this.maskcallers[caller]) return;
			this.maskcallers[caller] = false;
			this.maskcalls--;
			if(this.maskcalls) return;
			$('msk').hide();
		},
		wait:function(){
			if($('tlb-ul0')) $('tlb-ul0').style.display = 'none';
			$('wait').show();
		},
		unwait:function(){
			if($('tlb-ul0')) $('tlb-ul0').style.display = 'block';
			$('wait').hide();
		}
	});
	
	Smernel.observe('wait', Chrome.wait);
	Smernel.observe('unwait', Chrome.unwait);
	if(Prototype.Browser.WebKit) window.prefix(function(){document.include_css(Smernel.chrome_path + '/css/webkit.css')});
	window.append(function(){if(!Chrome.loaded){Chrome.load()}});


/* -------------------------------------------------------------------------------------------
ITEMWINDOW

------------------------------------------------------------------------------------------- */

	var ItemWindow = Class.create({
		
		cur:{
			divs:{},
			width:600,
			height:440
		},
		
		offset:{
			x:0,
			y:0
		},
		
		view:0,
		
		initialize:function(parentElement, options) {
			this.id = parentElement.id+'Window';
		    this.body = mk('div', {id:this.id}, {padding:'5px', textalign:'left'});
			parentElement.appendChild(this.body);
			Object.extend(this, options);
			this.load();
		},
		
		load:function(){
			if((this.Selection)&&(this.Selection.allow_multi)){
				Event.observe(window, 'keydown', this.shiftCheck.bind(this));
				Event.observe(window, 'keyup', this.shiftCheck.bind(this));
			}
			Event.observe(this.body, 'click', function(e){
				 if(e.target == this.body) this.blur()
			}.bind(this));
			this.loaded();
			this.draw();
		},
		
		loaded:function(){},
		
		shiftCheck:function(e){ 
			this.Selection.multi = cmdTest(e);
			if(!this.Selection.multi || e.keyCode != 65) return;
			Event.stop(e);
			this.selectall()
		},
		
		xy:function(){
			var w = this.cur.width - 20;
			var h = this.cur.height - 20;
			// maximum divs we can fit horizontally
			var mx = (w - (w % 84)) / 84;
			// maximum divs we can fit vertically
			var my = (h - (h % 84)) / 84;
			// maximum divs we can fit on screen
			var max = mx * my;
			// is there enough space? if not, expand y
			var l = this.Apps.length;
			if(l > max) my = my + parseInt((l-max)/mx) + 1;
			return {x:mx, y:my}
		},
		
		draw:function(){
			this.body.innerHTML = '';
			this.table = false;
			this.cur.divs = {};
			var i = 0,  A = this.Apps.members;
			if(!this.view) var xy = this.xy();
			for(var k in A){
				switch(this.view){
					case 0:
						var x = (i < xy.y) ? 0 : parseInt(i/xy.y);
						var y = (i < xy.y) ? i : (i % xy.y);
						var el = this.cur.divs[k] = this.mkAppDiv(A[k], x, y);
						break;
					case 1:
						var el = this.cur.divs[k] = this.mkAppTr(A[k], i);
				}
				Event.observe(el, 'mouseover', this.appOnOver.bind(el));
				Event.observe(el, 'mouseout', this.appOnOut.bind(el));
				el.click = this.appOnClick.bind(el);
				Event.observe(el, 'click', el.click);
				i++;
			}
			Smernel.fire(this.id+':drawn');
		},
		
		redraw:function(){
			this.draw();
			if(this.Selection){
				var D = this.cur.divs;
				for(var k in this.Selection.members){
					if(!D[k]) Selection.remove(k);
					else D[k].className = D[k].className.split('-')[0] + '-sel';
				}
			}	
		},
		
		blur:function(el){
			if(this.Selection) this.Selection.length = 0
			var D = this.cur.divs;
			for(var k in D) D[k].className = D[k].className.split('-')[0];
			if(!el) return;
			el.className = el.className.split('-')[0] + '-sel';
			if(this.Selection) this.Selection.set(el);
		},
		
		selectall:function(){
			if(!this.Selection) return;
			for(var k in this.cur.divs) this.cur.divs[k].click(false);
		},
		
		createTable:function(){
			this.table = new Table({className:'DskTable'});
			this.body.appendChild(this.table);	
		},
		
		switchTo:function(n){
			if(this.view == n) return;
			this.view = n;
			this.draw();
		},
		
		mkAppDiv:function(c, x, y){
			var d = this.body.mk('div', {
				iw:this,
				title:c.title || c.name,
				app:c,
				className:(c.highlighted ? 'DskApp-hl' : 'DskApp'),
				selected:function(){
					return (this.className.split('-')[1] == '-sel');
				}
			}, {
				backgroundImage:"url(" + c.icon + ")",
				left:this.offset.x + ((c.x) ? c.x : 10 + (x * 84) ) + 'px',
				top:this.offset.y +  ((c.y) ? c.y : 10 + (y * 84) ) + 'px'
			});
			Object.extend(d, c);
			var da = d.mk('div', {}, {
				height:'32px',
				width:'32px',
				margin:'4px 19px 15px',
				background:(c.overlay ? "url(" + c.overlay + ")" : 'none')
			});
			if(c.overlay) da.style.background = "url(" + c.overlay + ")";
			var db = d.mk('div', {}, {
				textAlign:'center'	
			});
			var span = db.mk('span', {
				innerHTML:c.name
			});
			c.el = d;
			return d;
		},
		
		mkAppTr:function(c, i){
			if(!this.table) this.createTable();
			var tr = this.Apps.makeTR(this, c, i)
			tr.app = c
			tr.iw = this
			tr.selected = function(){
				return (this.className.split('-')[1] == 'sel');
			}
			c.el = tr;
			return tr;
		},
		
		appOnOver:function(e){
			var a = this.className.split('-');
			if(!['sel', 'hl'].include(a[1])) this.className = a[0]+'-mo';
		},

		appOnOut:function(e){
			var a = this.className.split('-');
			if(!['sel', 'hl'].include(a[1])) this.className = a[0];
		},
		
		appOnClick:function(e){
			if(this.disabled || this.highlighted) return;
			if(e){
				if(!this.iw.Selection || (!this.iw.Selection.multi)) this.iw.blur();
				// set default vars here for tidiness
				if(!this.ct) this.lt = this.ct = 1;
				// click time, for handling double clicks
				var ct = new Date().getTime();
				// less than n milliseconds since last click?  Honor the intention
				if(ct < this.ct + 500){
					Event.stop(e);
					// triple clicks can take us to /location/location, and using arguments.callee.done 
					// means div remains locked after back button, so another time value (locktimer):
					if(this.lt > this.ct) return;
					this.lt = this.ct + 1000;
					this.app.click();
				}
				// else it's a single click
				this.ct = ct;
			}
			if(this.iw.Selection) this.iw.Selection.set(this.app);
			this.className = this.className.split('-')[0] + '-sel';
		}		
	});

/* -------------------------------------------------------------------------------------------
APPS

------------------------------------------------------------------------------------------- */
	var StringIndexed = Class.create({
		obj:{},
		initialize:function(o){
			Object.extend(this, o||{});
			['first', 'last', 'length', 'index', 'members'].each(function(f){ this.__defineGetter__(f, this['get'+f]) }.bind(this));
			this.__defineSetter__('length', this['setlength']);
		},
		set:function(k, v){
			if(!v) v = k, k = k.id;
			this.obj[k] = v;
			this.updated();
		},
		remove:function(k){
			if(!this.obj[k]) return;
			var o = {}
			for(b in this.obj) if(b != k) o[b] = this.obj[b];
			this.obj = o;
			this.updated();	
		},
		getfirst:function(){
			for(var k in this.obj) return this.obj[k];
		},
		getlast:function(){
			for(var k in this.obj){}
			return k ? this.obj[k] : null;
		},
		getlength:function(){
			var l=0
			for(k in this.obj) l++;
			return l;
		},
		setlength:function(n){
			var o = this.obj, i=0;
			this.obj = {}
			for(var k in o){
				if(i>=n) return;
				this.obj[k] = o[k];
				i++;
			}
			this.updated();
		},
		getindex:function(){
			var a = []
			for(var k in this.obj) a.push(k);
			return a;
		},
		getmembers:function(){
			return this.obj;
		},
		_each:function(iterator){
			for(var k in this.obj) iterator(this.obj[k]);
		},
		sortByMember:function(member, desc){
			var a = this.sortBy(function(b){ return b[member]; }), c = {};
			if(desc) a.reverse();
			for(var k in a) if(!Object.isFunction(a[k])) c[k] = a[k]
			this.obj = c;
		},
		updated:function(){}
	});
	Object.extend(StringIndexed.prototype, Enumerable);

/* -------------------------------------------------------------------------------------------
APPS

------------------------------------------------------------------------------------------- */
	
	var AppsContainer = Class.create(StringIndexed, {
		load:function(apps){
			this.length = 0;
			this.obj = {}
			this.add(apps)
		},
		add:function(apps){
			apps = this.filter(apps);
			for(var k in apps){
				this.obj[k] = new AppConstructor(apps[k], this);
				this.length++
			}
			this.loaded();
		},
		filter:function(apps){ return apps },
		loaded:function(){},
		clickHandler:function(){},
		makeTr:function(iw, a, i){
			return iw.table.tr(i);
		}
	});
	
	var Apps = new AppsContainer;

	var AppConstructor = function(o, a){
		for(var k in o) this[k] = o[k]
		this.icon = (this.icon && this.icon.substr(0, 7) == 'http://') ? this.icon : Chrome.app_icon(this);
		this.click = a.clickHandler.bind(this);
	}


/* -------------------------------------------------------------------------------------------
SELECTION

------------------------------------------------------------------------------------------- */

	var SelectionContainer = Class.create(StringIndexed, {
		multi:false,
		allow_multi:false
	});

	function Selected(f){
		return (f) ? Selection.first : Selection.members;	
	}

/* -------------------------------------------------------------------------------------------
CLIPBOARD

------------------------------------------------------------------------------------------- */

	var ClipboardContainer =  Class.create(StringIndexed, {
		action:'copy',
		initialize:function($super, o){
			$super(o);
			this.read();
		},
		cut:function(){
			this.action = 'cut';
			this.getSelection();
		},
		copy:function(){
			this.action = 'copy';
			this.getSelection();
		},
		paste:function(){
			Smajax.exec('paste', {items:this.obj, action:this.action});
		},
		getSelection:function(){
			this.set(Selection.index)
		},
		set:function(a){
			this.obj = {};
			if(typeof a == 'string') this.obj[a] = a;
			else a.each(function(v){ this.obj[v] = v }.bind(this));
			this.write()
		},
		write:function(){
			this.setCookie('clipboard_action', this.action);
			// $A() fails on smrfs for some reason
			var a = [], o = this.obj;
			for(var k in o) a.push(o[k]);
			this.setCookie('clipboard_content', a.join(','));
			Smernel.fire('Clipboard:written');
		},
		setCookie:function(n, v){
			$C(n).set({value:v, expires:0, path:'/'});	
			$C(n).write();
		},
		read:function(){
			var cc = $C('clipboard_content').value;
			var ca = $C('clipboard_action').value;
			if(ca) this.action = ca;
			if(cc) this.set(cc.split(','))
		},
		clear:function(){
			this.length = 0;
			this.write()
		}
	});

	

	/* -------------------------------------------------------------------------------------------
	TOOLBAR

	------------------------------------------------------------------------------------------- */

		var ToolbarContainer = function(ops){
			this.sub = {};
			this.ops = ops || {};
			this.el = mk('ul', {
				id:'tlb-ul0',
				className:'tlb'
			});
			$('tlb').appendChild(this.el);
			this.load = function(ops){
				for(var n in ops){
					this.sub[n] = new ToolbarOp(ops[n], n);
					this.sub[n].el.className = 'tlb0';
					this.el.appendChild(this.sub[n].el);
					this.sub[n].armed(1)
				}
			}
			this.load(this.ops)
			this.option = function(n){
				if(is_null(this.sub[n])){
					var ops = {}
					ops[n] = {}
					this.load(ops)
				} 
				return this.sub[n];
			}
		}

		var ToolbarOp = function(op, n){
			this.t = 'f';
			this.o = '';
			this.readop = function(k){
				switch(true){
					case Object.isUndefined(this[k]):
						return true;
					case Object.isString(this[k]):
						return eval(this[k]);
					case Object.isFunction(this[k]):
						return this[k]();
					default:
						return (true == this[k]);
				}
			}
			this.armable = function(){
				return this.readop('e');
			}
			this.displayable = function(){
				return this.readop('d');
			}
			this.doit = function(){
				return ((this.t != 'f') || (!this.armed())) ? false : this.readop('o');
			}
			this.classname = function(){
				return this.isMenu() ? 'tlb-menu' : 'tlb-func' ;
			}
			this.armed = function(){
				var arm = this.armable();
				this.span.className = this.classname() +  (arm ? '' : '-disarmed');
				this.el.style.display = this.displayable() ? 'block' : 'none';
				if(this.isMenu()) for(var m in this.sub) this.sub[m].armed()
				return arm;
			}
			this.option = function(n){
				if(!this.isMenu()) this.toMenu();
				if(is_null(this.o[n])){
					this.o[n] = new ToolbarOp({}, n);
					this.ul.appendChild(this.o[n].el);
				} 
				return this.o[n];
			}
			this.toFunction = function(op){
				this.t = 'f';
				Object.extend(this, op||{});
			}
			this.toMenu = function(){
				this.t = 'm';
				if(typeof this.o != 'object') this.o = {};
				if(this.ul) return;
				this.ul = this.el.mk('ul');
				this.sub = {};
			}
			this.isMenu = function(){
				return this.t == 'm';
			}
			Object.extend(this, op||{});
			this.el = mk('li');
			this.span = this.el.mk('span', {
				innerHTML:n,
				tlbOp:this
			});
			if(this.isMenu()){
				this.toMenu();
				for(var n in this.o){
					this.sub[n] = new ToolbarOp(this.o[n], n);
					this.ul.appendChild(this.sub[n].el);
				}
			}
			Event.observe(this.span, 'mouseover', this.armed.bind(this));
			Event.observe(this.span, 'click', this.doit.bind(this));
		}


/* -------------------------------------------------------------------------------------------
TUX

------------------------------------------------------------------------------------------- */

		var TUX = {
			status:'quiet',
			stati:['quiet','appearing','speaking','fading'],
			asserting:false,
			get busy(){
				return (TUX.asserting || ['apearing','fading'].include(TUX.status));	
			},
			get speaking(){
				return ['apearing','speaking'].include(TUX.status);
			},
			transitionEvents:{
				appearing:function(){
					$('spkbk').show();
					Chrome.mask('TUX');
					TUX.status = 'appearing';	
				},
				appeared:function(){
					// let him get a word in
					$('spk').show()
					setTimeout(function(){ 
						addEvent($('msk'), 'click', TUX.bequiet);
						TUX.status = 'speaking';
					}, 500);
				},
				fading:function(){
					$('spk').hide()
					removeEvent($('msk'), 'click', TUX.bequiet);
					TUX.status = 'fading';	
				},
				faded:function(){
					$('spkbk').hide();
					Chrome.unmask('TUX');
					TUX.clear();
					TUX.status = 'quiet';
					TUX.asserting = false	
				}
			},
			transitions:{
				fadeIn:function(){
					var dims = $('spk').getDimensions()
					new Effect.Morph($('spkbk'),
						{
							style:{
								height:dims.height+'px',
								width:dims.width+'px'
							},
							duration:0.3,
							fps:30,
							beforeStart:TUX.transitionEvents.appearing,
							afterFinish:TUX.transitionEvents.appeared
						}
					);
				},
				fadeOut:function(){
					$('spk').style.width = '400px';
					new Effect.Morph($('spkbk'),
						{
							style:{
								height:'0px',
								width:'0px'
							},
							duration:0.3,
							fps:30,
							beforeStart:TUX.transitionEvents.fading,
							afterFinish:TUX.transitionEvents.faded
						}
					);
				}
			},
			phrases:{
				'yo':'wattup?'
			},
			learn:function(phrase, dialogue){
				this.phrases[phrase] = dialogue;	
			},
			forget:function(phrase){
				this.phrases[phrase] = false;
			},
			recall:function(phrase, options){
				return (typeof this.phrases[phrase] == 'function') ? this.phrases[phrase](options) : this.phrases[phrase];
			},
			say:function(phrase, options){
				if(!this.phrases[phrase]) return this.justsay("I don't know how to say '"+phrase+"'");
				this.interject(this.recall(phrase, options))
			},
			justsay:function(sentence){
				this.interject(sentence);
			},
			interject:function(dialogue){
				if(this.busy) return setTimeout(function(){ TUX.interject(dialogue) }, 500);
				this.clear();
				if(typeof dialogue == 'string')
					this.Elements.spkwrap.innerHTML = dialogue;
				else
					try{
						this.Elements.spkwrap.appendChild(dialogue.self)
					}catch(e){ return };
				this.transitions.fadeIn()
			},
			clear:function(){
				if(TUX.Elements.spkwrap.firstChild){
					if(TUX.dialogue) TUX.dialogue.destroy()
					TUX.Elements.spkwrap.removeChild(TUX.Elements.spkwrap.firstChild)
				}
				TUX.Elements.spkwrap.innerHTML = '';	
			},
			click:function(e){
				if(e && e.keyCode != 13) return;
				if(TUX.dialogue && TUX.dialogue.defaultButton) return TUX.dialogue.defaultButton.click()
				TUX.bequiet()
			},
			get dialogue(){
				if(TUX.Elements.spkwrap.firstChild && TUX.Elements.spkwrap.firstChild.creator) return TUX.Elements.spkwrap.firstChild.creator
				return false
			},
			speak:function(){
				if(!TUX.busy) TUX.say('hello');
			},
			bequiet:function(){
				if(!TUX.busy) TUX.transitions.fadeOut();
				if(TUX.asserting) $('upbg').flash('orange');
			}
		}	

		TUX.input = Class.create({
			initialize:function(options){
				this.creator = this
				this.options={
					type:'text',
					className:'tuxinp',
					value:'',
					onchange:Prototype.emptyFunction
				}
				Object.extend(this.options, options || {});
				this.self = mk('input', {creator:this});
				Object.extend(this.self, this.options)
				Event.observe(this.self, 'change', this.change.bind(this.self));
				this.__defineSetter__('value', function(v){ this.self.value = v })
				this.__defineSetter__('onchange', function(f){ this.self.onchange = f })
			},
			change:function(){
				this.creator.flash();
				if(this.self.onchange) this.self.onchange()
			},
			flash:function(color){
				if(this.flashing || !this.self.id) return
				this.flashing = true
				if(!color) color = '#ffff99'
				new Effect.Morph(this.self.id, {
					style: 'background:'+color,
					duration: 0.3,
					afterFinish:this.creator.flashOff.bind(this)
				});
			},
			flashOff:function(){
				new Effect.Morph(this.self.id, {
					style: 'background:#ffffff;',
					duration: 0.3,
					afterFinish:this.creator.flashDone.bind(this)
				});
			},
			flashDone:function(){
				this.flashing = false
			},
			set:function(options){
				Object.extend(this.self, options)
			},
			destroy:function(){
				Event.stopObserving(this.self, 'change', this.change)
			}
		});

		TUX.button = Class.create({
			initialize:function(options){
				this.creator = this
				this.options={
					value:'OK',
					target:Prototype.emptyFunction,
					onSubmit:Prototype.emptyFunction,
					type:'button',
					className:'tuxbtn',
					send:true
				}
				Object.extend(this.options, options || {});
				this.self = mk('input', {creator:this});
				Object.extend(this.self, this.options)
				Event.observe(this.self, 'click', this.click.bind(this.self))
			},
			click:function(){
				this.creator.flash();
				this.self.target(typeof this.self.send == 'function' ? this.self.send() : this.self.send)
				this.self.onSubmit();
			},
			flash:function(){
				if(this.flashing) return
				this.flashing = true
				this.self.style.color = 'green';
				setTimeout(function(){ 
					this.self.style.color = "#222222"
					this.flashing = false
				}.bind(this), 300);
			},
			set:function(options){
				Object.extend(this.self, options)
			},
			destroy:function(){
				Event.stopObserving(this.self, 'click', this.click)
			}
		});

		TUX.Bubble = Class.create({
			initialize: function(options) {
				this.options={
					header:'',
					body:'',
					footer:'',
					target:TUX.bequiet,
					onSubmit:TUX.bequiet,
					buttons:{OK:true},
					defaultButton:'OK',
					inputs:false
				};
				this.inputs = {}
				this.buttons = {}

				Object.extend(this.options, options || {});
				this.self = mk('div', {
					id:'tuxbubble',			  
					creator:this
				});	
				this.header = this.self.mk('h3', {
					id:'tuxheader',
					innerHTML:this.options.header								
				});
				this.body = this.self.mk('div', {
					id:'tuxbody',
					innerHTML:this.options.body,
					creator:this					
				});
				this.footer = this.self.mk('div', {
					id:'tuxfooter',
					innerHTML:this.options.footer
				});
				if(this.options.inputs){
					this.body.innerHTML += '<br /><br /><br /><br /><br /><br /><br /><br />'
					for(id in this.options.inputs){
						ops = this.options.inputs[id];
						if(typeof ops !== 'object'){
							ops = {}
							ops.value = this.options.inputs[id]
						}
						ops.id = id;
						this.inputs[id] = new TUX.input(ops)
						this.body.mk('div', {className:'tuxinpwrap'}).appendChild(this.inputs[id].self)
					}
				}
				if(this.options.buttons){
					var btnwrap = this.body.mk('div', {className:'tuxbtnwrap'});
					for(value in this.options.buttons){
						ops = this.options.buttons[value];
						if(typeof ops !== 'object'){
							ops = {}
							ops.send = this.options.buttons[value]
						}
						ops.value = value
						if(!ops.onSubmit) ops.onSubmit = this.options.onSubmit
						if(!ops.target) ops.target = this.options.target
						this.buttons[value] = new TUX.button(ops)
						btnwrap.appendChild(this.buttons[value].self)
					}
					if(this.buttons[this.options.defaultButton]) this.defaultButton = this.buttons[this.options.defaultButton]
				}
			},
			append:function(str){
				this.footer.innerHTML = str;
				TUX.transitions.fadeIn()
			},
			hasInputs:function(){
				for(var k in this.inputs) return true
				return false
			},
			destroy:function(){
				for(k in this.buttons) this.buttons[k].destroy()
				for(k in this.inputs) this.inputs[k].destroy()
				this.destroyed = true
			}
		});

		var TUXalert = function(sentence, callback, options){
			options = options || {}
			options.body = sentence
			if(callback) options.target = callback
			var dialogue = new TUX.Bubble(options);
			TUX.interject(dialogue);
			return dialogue;
		}

		var TUXconfirm = function(sentence, callback, options){
			options = options || {}
			options.body = sentence
			if(!options.buttons) options.buttons = {OK:1, Cancel:0};
			if(callback) options.target = callback
			var dialogue = new TUX.Bubble(options);
			TUX.interject(dialogue);
			return dialogue;
		}

		var TUXprompt = function(sentence, suggest, callback, options){
			options = options || {}
			options.body = sentence
			if(is_null(suggest)) suggest = ''
			if(!options.inputs) options.inputs = {tuxprompt:suggest}
			if(!options.buttons) options.buttons = {OK:function(){ return $('tuxprompt').value }, Cancel:0};
			if(callback) options.target = callback
			var dialogue = new TUX.Bubble(options);
			TUX.interject(dialogue);
			return dialogue;
		}

		TUX.phrases.hello = function(){
			var s = '<div align="left">'
			+ '<div style="min-height:140px">'
			+ '<img src="' + Chrome.icon_uri((usr().is_root ? 'Root' : 'User') + '.png', 128) + '" style="float:left; margin:-5px 5px 5px -5px" />'
			+ '<h1>Hey, ' + usr().name + '!</h1>'
			+ '<br /><br />You logged in at ' + UnixToTime(usr().last_login) + '<br />';
			if(usr()['class'] == 'Sudoer' && usr().is_root){
				s += '<small>Your sudo session expires in ' + Math.floor( ((usr().sudo_expires*1000)-new Date().getTime())/60000) 
				+ ' minutes</small><br />'; 
			}
			s += '</div><div style="padding-left:40px">'
			+ 'Tasks:<div style="padding-left:20px">'
			+ '-> <a href="javascript:TUX.say(\'authenticate\');">switch user</a><br />'
			+ '-> <a href="javascript:TUX.say(\'usurpas\');">view usurpas</a><br />'
			+ '-> <a href="javascript:TUX.say(\'pubkey\');">view pubkey</a><br />'
			+ '-> <a href="javascript:Smajax.ping(1)">ping server</a><br />'
			+ '-> <a href="javascript:TUX.say(\'smitten\');">smitten...</a><br />'
			+ '</div></div><br /><br /></div>';	
			return new TUX.Bubble({body:s})
		}

		TUX.phrases.authenticate = function(){
			// Get the salt
			if(!Chrome.salt) getsalt();
			// Set the callback
			var ops = {}
			ops.onSubmit = Prototype.emptyFunction
			ops.target = function(){
				var n = $('uname').value.trim();
				var p = $('upass').value.trim();
				if(!n || !p){
					$('upbg').flash('orange');
					return;
				}
				Smajax.login(n, hex_sha1(hex_sha1(p)+Chrome.salt));
			}
			ops.header = 'Login:';
			ops.inputs = {uname:usr().name, upass:{type:'password'}}
			if(Smernel.secure) ops.buttons = {OK:1,Cancel:{target:TUX.bequiet}}
			else $('upbg').flash('orange');
			Smernel.observe('usr:loaded', function(){
				TUX.asserting = false;
				TUX.bequiet();
			})
			return new TUX.Bubble(ops)
		}

		TUX.phrases.pubkey = function(){
			var s = '<div align="left">'
			+ '<div style="min-height:140px">'
			+ '<h3>Session Public Key</h3>'
			+ '<br /><small>Not to be confused with an RSA public key (it\'s a misnomer, sorry), this token allows you to see usurpas from the home domain</small>'
			+ '<br /><br />Currently: '+pubkey()+'<br /><br />'
			+ '<small>Your cookie should already be set in this browser.  '
			+ 'To set it in another, copy and paste the address below into the browser\'s address bar:</small><br /><br />'
			+ '<input type="text" readonly="readonly" style="width:400px" onclick="this.select()" value="http://'+DOM.name+'/?pubkey='+pubkey()+'&set_pubkey_and_exit=1" />'
			+ '<br /><br /><small>To view the site without usurpas, at any address set/append smuoi_usurpas=off to the query string, e.g:<br /><br />'
			+ '<a href="http://'+DOM.name+'/?smuoi_usurpas=off" target="_blank">http://'+DOM.name+'/?smuoi_usurpas=off</a> '
			+ '<br /><br />(or just sign out from Smuoi)</small>'
			+ '</div>'
			return new TUX.Bubble({body:s})
		}
		
		TUX.phrases.smitten = function(){
			var div = mk('div')
			var iframe = Smernel.mkIframe({height:'500px', width:'400px'}, '/@smitten?fmrp=@'+getcwd());
			$('spk').style.width = '800px';//= iframe.style.minWidth 
			div.appendChild(iframe);
			return new TUX.Bubble({body:div.innerHTML, buttons:false})
		}

		TUX.phrases.usurpas = function(){
			Smajax.exec('usurpas', {}, '/');
		}

		window.queue(function(){
			if(!usr()) return	
			TUX.Elements = (function(){
				var o = {
					spk:$('spk'),
					spkbk:$('spkbk'),
					spktable:new Table({width:'100%'}),
					upng:$('upng'),
					upbg:$('upbg')
				}
				Object.extend($('spkbk').style, {width:'0px',height:'0px'});
				o.spktd = o.spktable.tr().td(0, {id:'spktd', align:'center', valign:'center', 'height':300, 'width':400});
				o.spkwrap = mk('div', {id:'spkwrap'});
				o.spktd.appendChild(o.spkwrap);
				o.spk.appendChild(o.spktable);
				var mu = function(){
					if(!TUX.speaking) $('upbg').style.opacity = 0.3;									 
				};
				Event.observe(o.upng, 'mouseover', mu);
				Event.observe(o.upng, 'mousedown', function(){
					if(!TUX.speaking) $('upbg').style.opacity = 0.6;									 
				});
				Event.observe(o.upng, 'click', function(){
					if(!TUX.speaking) TUX.speak();									 
				});
				Event.observe(o.upng, 'mouseup', mu);
				Event.observe(o.upng, 'mouseout', function(){
					if(!TUX.speaking) $('upbg').style.opacity = 0;									 
				});
				o.upbg.flash = function(color){
					if(this.flashing) return;
					var args = {pulses:3, duration:1.5};
					if(color){
						var oldColor = this.style.backgroundColor;
						args.afterFinish = function(){ 
							$('upbg').style.backgroundColor = oldColor 
							$('upbg').flashing = false;
						};
						this.flashing = true;
						this.style.backgroundColor = color;
					}
					this.pulsate(args);
				}
				return o;
			})();
			TUX.new_user = function(){
				TUX.asserting = false
				TUX.bequiet();
				$('upng').style.backgroundImage = 'url(' + Chrome.icon_uri(((usr().is_root) ? 'Root.png' : 'User.png')) + ')';	
				if(usr().fresh) TUX.say('hello');
			}
			TUX.new_user()
			Smernel.observe('loggedin', TUX.new_user);
			Student = TUX;
			say = TUX.say.bind(TUX), learn = TUX.learn.bind(TUX), forget = TUX.forget.bind(TUX), justsay = TUX.justsay.bind(TUX);
			Event.observe(document, 'keydown', TUX.click)
			Smernel.callAuthHandler = function(){
				TUX.say('authenticate');	
				TUX.asserting = !Smernel.secure;
			}
		});

/* -------------------------------------------------------------------------------------------
CMDLINE

------------------------------------------------------------------------------------------- */

		var CmdLine = {
			dir:null,
			init:function(){
				if(is_null(this.dir)) this.dir = getcwd();
			},
			ori:function(){
				this.init();
				return usr().name + '@' + DOM.id + ' [' + this.dir + ']#';
			},
			send:function(cmd, cb){
				this.init();
				id = O_ID ? O_ID : '';
				if(!cb) cb = Prototype.emptyFunction;
				Smajax.exec('command', {id:id,cmd:cmd,dir:this.dir}, '/', cb);
			}
		}


/* -------------------------------------------------------------------------------------------
NAMER

------------------------------------------------------------------------------------------- */
		var Namer = {

			begin:function(id){
				if(!id) id = O_ID;
				if(!id) return;
				this.id = id;
				this.json = false
				this.overwrite = false
				this.names = []
				justsay('<h3>wait...</h3><br /><div class="wait"></div>');
				Smajax.exec('findme', {id:id}, '/@Nicename', this.prompt.bind(this));
			},

			prompt:function(json){
				this.json = json;
				this.names = json.details.names
				var name = '';
				var body = '<small>Omit the "my" prefix</small><br /><br />';
				if(this.names.length){
					name = this.names[0]
					body += 'current names:<ol>'
					this.names.each(function(name){ body += '<li>' + name + '</li>' });
					body += '</ol><br />';
				}
				this.dialogue = new TUX.Bubble(
					{
						header:'Give this resource a Nice Name:',
						body:body,
						inputs:{nicename:name},
						buttons:{OK:function(){ $('nicename').creator.change(); return $('nicename').value }, Cancel:{target:TUX.bequiet}},
						onSubmit:Prototype.emptyFunction,
						target:this.submit.bind(this)
					}
				);
				this.input = this.dialogue.inputs['nicename'].self;
				this.OK = this.dialogue.buttons['OK'].self;
				this.input.onchange = Namer.clean.bind(this.input)
				TUX.interject(this.dialogue)
			},

			submit:function(n){
				if(!n || this.names.include(n)) return;
				var o = (!this.dooverwrite(n) || this.json.code == 2) ? '' : 1;
				this.dialogue.append('');
				Smajax.exec('nameme', {id:O_ID,name:n,overwrite:o}, '/@Nicename', this.update.bind(this));
			},

			dooverwrite:function(n){
				return (n && this.overwrite == n)	
			},

			update:function(json){
				this.json = json
				switch(json.code){
					case 3:
						this.overwrite = this.input.value;
						var s = '<div align="left">The name "' + json.details.name + '" already exists (' + json.details.typename + '). Overwrite?</div>';
						break;
					case 1:
						var s = 'You named the resource.  Hooray!';
						this.OK.target = TUX.bequiet
						break;
					default:
						var s = 'the resource was not created, perhaps an error';
				}
				this.dialogue.append(s);
			},

			clean:function(){
				// Strangely, a single call to replace non-alphanumeric characters doesn't do the trick, so a while loop is used
				while(this.value.match(/[^A-Za-z0-9_\.]/)) this.value = this.value.replace(/[^A-Za-z0-9_\.]/, '');
				while(this.value.match(/\.\./)) this.value = this.value.replace(/\.\./, '.');
				while(this.value.substr(0, 1) == '.') this.value = this.value.substr(1)
				while(this.value.substr(-1) == '.') this.value = this.value.substr(0, this.value.length-1)
			}
		}


/* -------------------------------------------------------------------------------------------
BROWSER

------------------------------------------------------------------------------------------- */

		var Browser = {

			built:false,
			
			init:function(){
				if(this.built) return;
				this.div = mk('div');
				this.div.id = 'Browser';
				$U('body').appendChild(this.div);
				this.div.innerHTML = 
					'<div id="BrowserTop">'
						+ '<span id="BrowserTitle"></span>'
						+ '<a href="javascript:Browser.hide()" id="BrowserClose">Close Window</a>'
					+ '</div>'
					+ '<div id="BrowserTlb"></div>'
					+ '<div id="BrowserCell"></div>'
					+ '<div id="BrowserBase"><div id="hbtns"></div><div id="sops" style="display:none"><input type="text" id="brinp" value="" onchange="$(\'brbtn\').disabled = !(this.value)" /> <input type="button" id="brbtn" value="save" onclick="Browser.onselect()" /></div></div>';
				this.titl = $('BrowserTitle');
				this.tlbr = $('BrowserTlb');
				this.cell = $('BrowserCell');
				this.base = $('BrowserBase');
				this.inp = $('brinp');
				this.btn = $('brbtn')
				this.Apps = window.AppsB = new AppsContainer;
				Smernel.fire('browser:loaded');
				this.built = true;
			},

			new_session:function(){
				this.init();
				this._CACHE = {};
				this.location = '/';
				this.task = '';
				this.btntxt = 'save';
				this.input = '';
				this.xargs = {};
				this.onselect = function(){};
				this.Apps.length = 0;
				this.history = new BHistory;
				this.clearscreen();
			},

			load:function(o){
				if(!this.built) this.new_session();
				for(var k in o) this[k] = o[k];
				this.titl.innerHTML = this.title
				this.inp.value = this.input;
				this.btn.value = this.btntxt;
				$('sops').style.display = (this.input || this.btntext) ? 'block' : 'none';
			},

			proceedto:function(b){
				if(this.location.substr(-1) != '/') b  = '/' + b;
				this.goto(this.location + b);
				this.history.showbuttons();
			},

			goto:function(L){
				if(!this.built) this.new_session();
				if(this.is_cached(L)){
					if(this.location == L) return this.appear();
					return this.loadcache(L)
				}
				this.location = L;
				this.history.reindex()
				this.go();
			},

			go:function(){
				ajaxExec('browse', this.xargs, this.location);
			},

			receive:function(d){
				this.docache(d);
				if(typeof d == 'string') return this.loadHTML(d);
				this.loadApps(d);
			},

			loadHTML:function(h){
				this.cell.innerHTML = h;
				this.appear();
			},

			loadApps:function(a){
				if(!this.MC) this.MC = new ItemWindow($('BrowserCell'), {
					Apps:this.Apps,
					Selection:false
				});
				this.Apps.load(a);
				this.MC.draw();
				this.appear();
			},

			loadTools:function(t){
				this.tlbr.innerHTML = this.history.buttons() + t;
			},

			docache:function(data){
				this._CACHE[this.location] = data;	
			},

			loadcache:function(L){
				this.location = L
				this.receive(this._CACHE[L]);
				this.history.reindex();
			},

			is_cached:function(L){
				return (Boolean)(this._CACHE[L]);
			},

			appear:function(){
				if(this.active) return;
				Chrome.mask('Browser');
				this.cell.style.background = '#fff';
				this.cell.style.opacity = 1;
				this.div.show();
				this.active = true;
			},

			clearscreen:function(){
				this.active = false;
				this.div.hide();
				this.cell.style.background = '#fff url(' + Chrome.icon_uri('wait.gif', 32) + ') center no-repeat';
				this.cell.style.opacity = 0.7;
			},

			hide:function(){
				Chrome.unmask('Browser');
				Browser.div.fade({duration:0.5, afterFinish:Browser.clearscreen.bind(Browser)});
			}

		}

/* -------------------------------------------------------------------------------------------
BROWSER HISTORY

------------------------------------------------------------------------------------------- */

		var BHistory = function(){

			this.i = 0;

			this.h = [];

			this.reindex = function(){
				var n = this.getIndexOf(Browser.location);
				if(n == -1) return this.write(Browser.location);
				this.setindex(n);
			}

			this.getIndexOf = function(L){
				for(var n=0; n<this.h.length; n++) if(this.get(n) == L) return n;
				return -1;
			}

			this.write = function(L){
				if(this.iskey(this.i+1) && (this.get(this.i+1) !== L)) this.forget();
				this.h.push(L);
				this.setindex(this.h.length-1)
			}

			this.forward = function(n){
				if(!n) n=1;
				if(!this.iskey(this.i+n)) return;
				this.setindex(this.i+n);
				Browser.goto(this.cur());
			}

			this.back = function(n){
				if(!n) n=1;
				if(!this.iskey(this.i-n)) return;
				this.setindex(this.i-n);
				Browser.goto(this.cur());
			}

			this.setindex = function(n){
				this.i = n;
				if($('fbtn')) $('fbtn').disabled = (!this.iskey(n+1));
				if($('bbtn')) $('bbtn').disabled = (!this.iskey(n-1));
			}

			this.cur = function(){
				return this.get(this.i);	
			}

			this.get = function(n){
				return this.h[n];	
			}

			this.iskey = function(n){
				return !(typeof this.h[n] == 'undefined');	
			}

			this.forget = function(){
				var a = [];
				for(var n=0; n<=this.i; n++) a[n] = this.h[n];
				this.h = a;
			}

			this.showbuttons = function(){
				this.mkbuttons();
				$('hbtns').show()
			}

			this.mkbuttons = function(){
				if($('bbtn')) return;
				var div = $('hbtns');
				div.style.display = 'none';
				var bbtn = mk('input');
				bbtn.type = 'button';
				bbtn.id = 'bbtn';
				bbtn.value = 'back';
				//bbtn.disabled = true;
				Event.observe(bbtn, 'click', function(){
					Browser.history.back(1);
				});
				div.appendChild(bbtn);
				div.appendChild(document.createTextNode(' '));
				var fbtn = mk('input');
				fbtn.type = 'button';
				fbtn.id = 'fbtn';
				fbtn.value = 'forward';
				fbtn.disabled = true;
				Event.observe(fbtn, 'click', function(){
					Browser.history.forward(1);
				});
				div.appendChild(fbtn)
			}
		}

